package com.example.filesystem.service;

import com.example.filesystem.dto.FileInfoDTO;
import com.example.filesystem.model.FileInfo;
import com.example.filesystem.model.UploadChunk;
import com.example.filesystem.model.UploadInfo;
import com.example.filesystem.model.User;
import com.example.filesystem.repository.FileInfoRepository;
import com.example.filesystem.repository.UploadChunkRepository;
import com.example.filesystem.repository.UploadInfoRepository;
import com.example.filesystem.repository.UserRepository;
import com.example.filesystem.util.AuthUtil;
import com.example.filesystem.util.ContentType;
import com.example.filesystem.util.Md5;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class FileService {

    @Value("${fileManager.upload.dir}")
    private String upload_dir;
    private static final ConcurrentHashMap<String, Integer> progressMap = new ConcurrentHashMap<>();

    Cache<Long, FileInfo> fileMap = Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.SECONDS) // 写入后10s过期
            .maximumSize(100) // 最大100个条目
            .build();

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm");


    private final UploadInfoRepository uploadInfoRepository;
    private final UserRepository userRepository;
    private final UploadChunkRepository uploadChunkRepository;
    private final FileInfoRepository fileInfoRepository;

    public FileService(UploadInfoRepository uploadInfoRepository, UserRepository userRepository, UploadChunkRepository uploadChunkRepository, FileInfoRepository fileInfoRepository) {
        this.uploadInfoRepository = uploadInfoRepository;
        this.userRepository = userRepository;
        this.uploadChunkRepository = uploadChunkRepository;
        this.fileInfoRepository = fileInfoRepository;
    }

    public FileInfo getPrepare(Long prepareId) {
        return fileMap.get(prepareId, f -> null);
    }

    public Long prepare(Long id) {
        String currentUsername = AuthUtil.currentUsername();
        FileInfo fileInfo;
        if ("admin".equals(currentUsername)) {
            fileInfo = fileInfoRepository.findByFileId(id);
        } else {
            Long currentUserId = userRepository.findByUsername(AuthUtil.currentUsername()).map(User::getId).orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
            fileInfo = fileInfoRepository.find(id, currentUserId);
        }

        if (fileInfo == null) {
            return 0L;
        }
        if (fileInfo.getFilePath() == null) {
            return 1L;
        }
        try {
            Resource resource = new UrlResource(getFilePath(fileInfo.getFilePath()).toUri());
            if (!resource.exists()) {
                return 1L;
            }
        } catch (MalformedURLException e) {
            return 1L;
        }

        long prepareId = LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli();

        fileMap.put(prepareId, fileInfo);
        return prepareId;
    }

    public List<FileInfoDTO> listFilesWithCategory() {
        List<FileInfo> files;
        if (AuthUtil.currentUsername().equals("admin")) {
            files = fileInfoRepository.findAll();
        } else {
            Long currentUserId = userRepository.findByUsername(AuthUtil.currentUsername())
                    .map(User::getId)
                    .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
            files = fileInfoRepository.findByUserId(currentUserId);
        }

        return files.stream().map(file -> {
                    FileInfoDTO dto = new FileInfoDTO();
                    dto.setId(file.getId());
                    dto.setFilename(file.getFilename());
                    dto.setSize(file.getSize());
                    dto.setFileType(file.getFileType());
                    dto.setCreator(file.getCreator());
                    dto.setUploadTime(file.getUploadTime());
                    return dto;
                }).sorted(Comparator.comparing(FileInfoDTO::getUploadTime).reversed())
                .collect(Collectors.toList());
    }


    public Path getFilePath(String filename) {
        return Paths.get(upload_dir).resolve(filename);
    }

    public boolean deleteFile(Long fileId) throws IOException {
        FileInfo fileInfo = fileInfoRepository.findById(fileId)
                .orElse(null);

        if (fileInfo != null) {
            String currentUsername = AuthUtil.currentUsername();
            Long currentUserId = userRepository.findByUsername(currentUsername)
                    .map(User::getId)
                    .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
            if (currentUserId == null || !currentUserId.equals(fileInfo.getUserId())) {
                if (!"admin".equals(currentUsername)) {
                    fileInfo = null;
                }
            }
        }

        if (fileInfo != null) {
            Path filePath = Paths.get(upload_dir).resolve(fileInfo.getFilePath());
            Files.deleteIfExists(filePath);

            fileInfoRepository.delete(fileInfo);
            return true;
        }
        return false;

    }

    @Transactional
    public String initUpload(String fileName, Long fileSize, Long lastModified, Integer chunkSize, Long categoryId) {
        String uploadId = Md5.encode(fileName + fileSize + lastModified);

        UploadInfo uploadInfo = new UploadInfo();
        uploadInfo.setUploadId(uploadId);
        uploadInfo.setFileName(fileName);
        uploadInfo.setFileSize(fileSize);
        uploadInfo.setChunkSize(chunkSize);
        uploadInfo.setCategoryId(categoryId);

        String currentUsername = AuthUtil.currentUsername();
        Long currentUserId = userRepository.findByUsername(currentUsername)
                .map(User::getId)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));

        uploadInfo.setUserId(currentUserId);
        uploadInfo.setCreator(currentUsername);
        uploadInfo.setStatus("uploading");
        uploadInfo.setCreatedAt(LocalDateTime.now());

        uploadInfoRepository.save(uploadInfo);
        progressMap.put(uploadId, 0);

        return uploadId;
    }

    @Async
    public Integer checkChunk(String uploadId, String hash, Integer chunkNumber) {
        UploadChunk one = uploadChunkRepository.checkChunk(uploadId, chunkNumber);
        if (one != null && hash.equals(one.getHash())) {
            UploadInfo uploadInfo = uploadInfoRepository.findById(uploadId)
                    .orElseThrow(() -> new IllegalArgumentException("Invalid upload ID"));
            //更新进度
            updateProgress(uploadInfo);
            return 1;
        }
        return 0;
    }

    @Async
    public void uploadChunk(String uploadId, String hash, Integer chunkNumber, MultipartFile chunk) {
        try {
            UploadInfo uploadInfo = uploadInfoRepository.findById(uploadId)
                    .orElseThrow(() -> new IllegalArgumentException("Invalid upload ID"));

            // 保存分片到临时目录
            Path chunkPath = Paths.get(upload_dir, "temp", uploadId, chunkNumber.toString());
            Files.createDirectories(chunkPath.getParent());
            Files.write(chunkPath, chunk.getBytes(), StandardOpenOption.CREATE);

            // 记录分片信息
            UploadChunk uploadChunk = new UploadChunk();
            uploadChunk.setUploadId(uploadId);
            uploadChunk.setHash(hash);
            uploadChunk.setChunkNumber(chunkNumber);
            uploadChunk.setChunkSize(chunk.getSize());
            uploadChunk.setStatus("uploaded");
            uploadChunkRepository.save(uploadChunk);
            //更新进度
            updateProgress(uploadInfo);

        } catch (IOException e) {
            throw new RuntimeException("Failed to save chunk", e);
        }
    }

    private void updateProgress(UploadInfo uploadInfo) {
        // 更新进度
        int receivedChunks = uploadChunkRepository.countByUploadId(uploadInfo.getUploadId());
        int totalChunks = (int) Math.ceil((double) uploadInfo.getFileSize() / uploadInfo.getChunkSize());
        int progress = (int) ((double) receivedChunks / totalChunks * 100);
        progressMap.put(uploadInfo.getUploadId(), progress);
    }

    @Transactional
    public String completeUpload(String uploadId) {
        UploadInfo uploadInfo = uploadInfoRepository.findById(uploadId)
                .orElseThrow(() -> new IllegalArgumentException("Invalid upload ID"));

        FileInfo fileInfo = new FileInfo();

        // 合并所有分片
        String name = uploadInfo.getFileName().substring(0, uploadInfo.getFileName().lastIndexOf("."));
        String fileExtension = uploadInfo.getFileName().substring(uploadInfo.getFileName().lastIndexOf("."));
        String username = AuthUtil.currentUsername();
        String formatDate = formatter.format(LocalDateTime.now());

        String storedFilename = username + "/" + name + "(" + formatDate + ")" + fileExtension;
        Path filePath = Paths.get(upload_dir, storedFilename);

        try {
            Files.createDirectories(filePath.getParent());
            Files.createFile(filePath);

            int totalChunks = (int) Math.ceil((double) uploadInfo.getFileSize() / uploadInfo.getChunkSize());
            for (int i = 0; i < totalChunks; i++) {
                Path chunkPath = Paths.get(upload_dir, "temp", uploadId, String.valueOf(i));
                byte[] chunkData = Files.readAllBytes(chunkPath);
                Files.write(filePath, chunkData, StandardOpenOption.APPEND);
                Files.delete(chunkPath);
            }
            // 保存文件信息
            fileInfo.setFilename(uploadInfo.getFileName());
            fileInfo.setFilePath(storedFilename);
            fileInfo.setSize(uploadInfo.getFileSize());
            fileInfo.setUploadTime(LocalDateTime.now());
            fileInfo.setUserId(uploadInfo.getUserId());
            fileInfo.setCreator(uploadInfo.getCreator());
            fileInfo.setFileType(ContentType.getContentType(filePath));
            fileInfoRepository.save(fileInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 删除临时目录
        try {
            Path path = Paths.get(upload_dir, "temp", uploadId);
            Files.walkFileTree(path, new SimpleFileVisitor<>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
            Files.deleteIfExists(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //删除上传状态记录
        uploadInfoRepository.deleteInfo(uploadId);
        //删除分片信息
        uploadChunkRepository.deleteChunk(uploadId);

        progressMap.remove(uploadId);

        return fileInfo.getId() == null ? null : fileInfo.getId().toString();
    }

    public int getUploadProgress(String uploadId) {
        return progressMap.getOrDefault(uploadId, 0);
    }
}